<?php
namespace my;
class Sdpay{
    public $mech_no = "15888484";//商户测试号

    private $puk = null; #公钥
    private $prk = null; #私钥
    private $pwd = "123456";#密码

    #发送头部
    public $head = [
      'version' => '1.0',
      'method' => '',  #接口名称
      'productId' => '', #产品编码
      'accessType' => 1, #1-普通商户接入（目前支持1）2-平台商户接入
      'mid' => '',
      'plMid' => '', #接入类型为2时必填
      'channelType' => '07',#07-互联网 08-移动端
      'reqTime' => 0,#请求时间
    ];

    public function __construct(){
        $this->head['mid'] = $this->mech_no;
        $this->head['reqTime'] = date('YmdHis', time());
        $this->puk = $this->loadX509Cert();
        $this->prk = $this->loadPk12Cert();
    }

    //查询余额
    public function query(){
        $aeskey = $this->randomStr(16);
        $params = [
            'transCode' => 'MBQU',
            'merId' => $this->mech_no,
            'url' => "https://caspay.sandpay.com.cn/agent-main/openapi/queryBalance",//商户余额查询
            'pt' => array(
                'orderCode' => com_order_no(),
                'version'   => '01',
                'productId' => '00000003',
                'tranTime'  => date('YmdHis', time())
            )
          ];

        $params['pt'] = json_encode($params['pt']);
        //AESKey 加密
        $encryptKey  = $this->RSAEncryptByPub($aeskey);
        //报文加密
        $encryptData = $this->AESEncrypt($params['pt'], $aeskey);
        //签名
        $sign = $this->make_sign($params['pt']);

        //post数据
        $returnData['transCode'] = $params['transCode'];
        $returnData['merId'] = $params['merId'];
        $returnData['encryptKey'] = $encryptKey;
        $returnData['encryptData'] = $encryptData;
        $returnData['sign'] = $sign;
        $returnData = http_build_query($returnData);

        //发送加密数据
        $res = $this->request_post($params['url'], $returnData);

        parse_str($res, $arr);
        if (empty($arr['encryptKey']) || empty($arr['encryptData']) || empty($arr['sign'])) {
            return com_error(['数据返回格式有误']);
        }
        //AESKey 解密
        $decryptAESKey = $this->RSADecryptByPri($arr['encryptKey']);
        //报文解密
        $decryptPlainText = $this->AESDecrypt($arr['encryptData'], $decryptAESKey);

        //验签
        $verify = $this->check_sign($decryptPlainText, $arr['sign']);
        if ($verify) {
            return $decryptPlainText;
        }else{
            return com_error(['验签错误']);
        }
    }

    /**
    * 代付业务,实时代付
    **/
    public function daifa($order_no,$money, $bank_username, $bank_no){
          $aeskey = $this->randomStr(16);
          $params = [
              'transCode' => 'RTPM',//实时代付
              'merId' => $this->mech_no,
              'url' => "https://caspay.sandpay.com.cn/agent-main/openapi/agentpay",
              'pt' => array(
                  'orderCode' => $order_no,
                  'version' => '01',
                  'productId' => '00000004',
                  'tranTime' => date('YmdHis', time()),
                  'tranAmt' => $money,
                  'currencyCode' => '156',
                  'accAttr' => '0',
                  'accNo' => $bank_no,
                  'accType' => '4',
                  'accName' => $bank_username,
      //        'provNo' => 'sh',
      //        'cityNo' => 'sh',
                  // 'bankName' => 'cbc',
                  // 'bankType' => '1',
                  'remark' => 'pay',
                  'payMode'=>'2'
              )
            ];
          $params['pt'] = json_encode($params['pt']);
          //AESKey 加密
          $encryptKey  = $this->RSAEncryptByPub($aeskey);
          //报文加密
          $encryptData = $this->AESEncrypt($params['pt'], $aeskey);
          //签名
          $sign = $this->make_sign($params['pt']);

          //post数据
          $returnData['transCode'] = $params['transCode'];
          $returnData['merId'] = $params['merId'];
          $returnData['encryptKey'] = $encryptKey;
          $returnData['encryptData'] = $encryptData;
          $returnData['sign'] = $sign;
          $returnData = http_build_query($returnData);

          //发送加密数据
          $res = $this->request_post($params['url'], $returnData);

          parse_str($res, $arr);
          if (empty($arr['encryptKey']) || empty($arr['encryptData']) || empty($arr['sign'])) {
              return com_error(['数据返回格式有误']);
          }
          //AESKey 解密
          $decryptAESKey = $this->RSADecryptByPri($arr['encryptKey']);
          //报文解密
          $decryptPlainText = $this->AESDecrypt($arr['encryptData'], $decryptAESKey);

          //验签
          $verify = $this->check_sign($decryptPlainText, $arr['sign']);
          if ($verify) {
              return $decryptPlainText;
          }else{
              return com_error(['验签错误']);
          }
    }
    /**
    * 网关支付
    **/
    public function gate_way_order_pay($body = [],$payMode = 'bank_pc'){
        $head = $this->head;
        $head['method'] = "sandpay.trade.pay";
        $head['productId'] = $payMode=='bank_pc' ? "00000007" : "00000008";

        #请求数据
        $request = json_encode(['head'=>$head, 'body'=>$body]);
        $sign = $this->make_sign($request);

        #post数据
        $post = [
          'charset' => 'utf-8',
          'signType' => '01',
          'data' => $request,
          'sign' => $sign
        ];
        //echo "<pre>";print_r($post);
        $query = http_build_query($post);

        $url = "https://cashier.sandpay.com.cn/gateway/api/order/pay";
        $response = $this->request_post($url, $query);
        parse_str(urldecode($response), $arr);
        $arr['sign'] =  str_replace(' ', '+', $arr['sign']);
        //echo "<pre>";print_r($arr);exit;
        //orderPay接口返回的credential字段里面的sign也是base64编码 也需将空格替换成+
        $data = json_decode($arr['data'], true);
        if ($data && isset($data['body']['credential'])) {
            $credential =json_decode($data['body']['credential'], true);
            if (isset($credential['params']['sign']) || isset($credential['params']['signature']) ) {
                //将字串中由于json_encode()后，产生的问题'\/'转换为正确的'/'。
                $data['body']['credential'] = str_replace("\\/", "/", json_encode($credential,JSON_UNESCAPED_UNICODE));
                //使用第二参数JSON_UNESCAPED_UNICODE,阻止json_encode()转译汉字
                $arr['data'] = str_replace(array("\\/", ' '), array("/", '+'),json_encode($data,JSON_UNESCAPED_UNICODE));
            }
        }
        //验签
        if($this->check_sign($arr['data'], $arr['sign'])){
            return $arr['data'];
        }else{
            return com_error(['验证签名错误']);
        }
    }
    public function randomStr($size=16){
        $str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        $arr = array();
        for ($i = 0; $i < $size; $i++) {
            $arr[] = $str[mt_rand(0, 61)];
        }
        $str = implode('', $arr);
        return $str;
    }

    /**
     * RSA私钥解密
     * @param $cipherText
     * @return string
     * @throws \Exception
     */
    public function RSADecryptByPri($cipherText) {
        if (!openssl_private_decrypt(base64_decode($cipherText), $plainText, $this->prk, OPENSSL_PKCS1_PADDING)) {
            return com_error(['AESKey 解密错误']);
        }
        return (string)$plainText;
    }

    /**
     * AES解密
     * @param $cipherText
     * @param $key
     * @return string
     */
    public function AESDecrypt($cipherText, $key) {
        $result = openssl_decrypt(base64_decode($cipherText), 'AES-128-ECB', $key, 1);
        if (!$result) {
            return com_error(['报文解密错误']);
        }
        return $result;
    }

    //AESKey 加密
    public function RSAEncryptByPub($str){
      if (!openssl_public_encrypt($str, $cipherText, $this->puk, OPENSSL_PKCS1_PADDING)) {
          return com_error(['AESKey 加密错误']);
      }
      return base64_encode($cipherText);
    }
    /**
     * AES加密
     * @param $plainText
     * @param $key
     * @return string
     */
    public function AESEncrypt($plainText, $key) {
        $result = openssl_encrypt($plainText, 'AES-128-ECB', $key, 1);
        if (!$result) {
            return com_error(['报文加密错误']);
        }
        return base64_encode($result);
    }

    /**
    * 扫码支付
    **/
    public function qr_order_pay($body){
          $head = $this->head;
          $head['method'] = "sandpay.trade.precreate";
          $head['productId'] = $body['payTool']=='0402' ? "00000005" : "00000006";

          #请求数据
          $request = json_encode(['head'=>$head, 'body'=>$body]);
          $sign = $this->make_sign($request);

          #post数据
          $post = [
            'charset' => 'utf-8',
            'signType' => '01',
            'data' => $request,
            'sign' => $sign
          ];
          //echo "<pre>";  print_r($post);
          $query = http_build_query($post);

          $url = "https://cashier.sandpay.com.cn/qr/api/order/create";
          $response = $this->request_post($url, $query);
          parse_str(urldecode($response), $arr);
          $arr['sign'] =  str_replace(' ', '+', $arr['sign']);
          //echo "<pre>";print_r($arr);exit;
          //orderPay接口返回的credential字段里面的sign也是base64编码 也需将空格替换成+
          $data = json_decode($arr['data'], true);
          if ($data && isset($data['body']['credential'])) {
              $credential =json_decode($data['body']['credential'], true);
              if (isset($credential['params']['sign']) || isset($credential['params']['signature']) ) {
                  //将字串中由于json_encode()后，产生的问题'\/'转换为正确的'/'。
                  $data['body']['credential'] = str_replace("\\/", "/", json_encode($credential,JSON_UNESCAPED_UNICODE));
                  //使用第二参数JSON_UNESCAPED_UNICODE,阻止json_encode()转译汉字
                  $arr['data'] = str_replace(array("\\/", ' '), array("/", '+'),json_encode($data,JSON_UNESCAPED_UNICODE));
              }
          }
          //验签
          if($this->check_sign($arr['data'], $arr['sign'])){
              return $arr['data'];
          }else{
              return com_error(['验证签名错误']);
          }
    }

    /**
     * 获取公钥
     */
    private function loadX509Cert() {
        $file = file_get_contents(__DIR__."/sd_rsa/sand-test.cer");
        $cert = chunk_split(base64_encode($file), 64, "\n");
        $cert = "-----BEGIN CERTIFICATE-----\n" . $cert . "-----END CERTIFICATE-----\n";

        $res = openssl_pkey_get_public($cert);
        $detail = openssl_pkey_get_details($res);
        openssl_free_key($res);

        if (!$detail) {
            return com_error(['loadX509Cert::openssl_pkey_get_details ERROR']);
        }
        return $detail['key'];
    }

    /**
     * 获取私钥
     */
    private function loadPk12Cert() {
        $file = file_get_contents(__DIR__."/sd_rsa/mid-test.pfx");
        if (!openssl_pkcs12_read($file, $cert, $this->pwd)) {
            return com_error(['loadPk12Cert::openssl_pkcs12_read ERROR']);
        }
        return $cert['pkey'];
    }

    /**
     * 生成签名
     */
    public function make_sign($plainText){
        $resource = openssl_pkey_get_private($this->prk);
        $result = openssl_sign($plainText, $sign, $resource);
        openssl_free_key($resource);

        if (!$result) {
            return com_error(['签名出错' . $plainText]);
        }
        return base64_encode($sign);
    }

    /**
     * 验证签名
     */
    public function check_sign($plainText, $sign){
        $resource = openssl_pkey_get_public($this->puk);
        $result = openssl_verify($plainText, base64_decode($sign), $resource);
        openssl_free_key($resource);
        if (!$result) {//todo cancel annotation
            return com_error(['签名验证未通过,plainText:' . $plainText . '。sign:' . $sign, '020002']);
        }
        return $result;
    }

    /**
     * 发送curl
     */
    public function request_post($url = '', $param = '') {
        $ch = curl_init();//初始化curl
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        //正式环境时解开注释
         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $data = curl_exec($ch);//运行curl
        curl_close($ch);

        if (!$data) {
            return com_error(['请求出错']);
        }
        return $data;
    }
}
